# 
# Copyright (C) 2006  Warren Konkel and Bounty Source, Inc.
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
# 

class SvnbrowserController < ApplicationController

	require 'svn/client'
	require 'svn/ra'
  # uses_component_template_root
  # layout $bs_svn_browser[:layout]
	layout 'standard'

  def distributor
    begin
			@repository = Repository.find_by_name(params[:repository_name])
      # this is where we'll be reading everything from
      @repo_path = $bs_svn_browser[:repository_path]
  
      # load up the repository
      @repo = Svn::Repos.open(@repo_path)
  
      if params['path'].length > 0 && params['path'][0][0..0] == '!'
        # parse the URL out and figure out the path/revision/action
        params['action'] = params['path'][0][1..-1]

				@sub_title = {
					'revision' => '',
					'tree' => 'Source browsing',
					'form' => '',
					'source' => 'File information',
					'history' => 'History',
					'blame' => 'File blame',
					'revdiff' => 'Revision differences',
					'diff' => 'File differences'
				}[params['action']] || 'TEST'
        case params['action']
          when 'revision'
            redirect_to($bs_svn_browser[:url_base] + '/!tree/' + params['path'][1])
          when 'form', 'tree', 'source', 'history', 'blame', 'revdiff', 'diff'
            @rev = params['path'][1].to_i
            @path = params['path'][2..-1] || []
            @info = get_info
            @revisions = get_revisions if @info[:file?]
          
            send(params['action'])
            render(:action => params['action']) unless performed?
          when 'dump'
            send('dump')
        end
      end
      
      if !performed?
        # no idea what they requested... redirect them to the most recent tree
        redirect_to($bs_svn_browser[:url_base] + '/!tree/' + @repo.youngest_rev.to_s)
      end
    rescue Exception => e
      render(:text => '<h1>SVN Error</h1> An SVN error has occured: ' + e.message, :layout => true)
    end
  end

  private
    # this processes the header
    def form
      params['src1_path'] = params['src1_path'][1..-1] if params['src1_path'][0..0] == '/'
      params['src2_path'] = params['src1_path'][1..-1] if params['src1_path'][0..0] == '/'
    
      case params['formact']
        when 'tree'
          redirect_to($bs_svn_browser[:url_base] + '/!tree/' + params['revision'])
        when 'source'
          redirect_to($bs_svn_browser[:url_base] + "/!source/#{params['src1_revision']}/#{params['src1_path']}")
        when 'blame'
          redirect_to($bs_svn_browser[:url_base] + "/!blame/#{params['src1_revision']}/#{params['src1_path']}")
        when 'history'
          redirect_to($bs_svn_browser[:url_base] + "/!history/#{params['src1_revision']}/#{params['src1_path']}?stop_revision=#{params['stop_revision']}")
        when 'revdiff'
          redirect_to($bs_svn_browser[:url_base] + "/!revdiff/#{@rev}/#{File.join(@path)}?rev1=#{params['prev_revision1']}&rev2=#{params['prev_revision2']}")
        when 'pathdiff'
          redirect_to($bs_svn_browser[:url_base] + "/!diff/#{params['src1_revision']}/#{params['src1_path']}?against_rev=#{params['src2_revision']}&against_path=#{params['src2_path']}")
      end
    end
  
    def tree
      # if there's no parent_id specified, start with a default one
      params['parent_id'] ||= 'svn0'

      if request.xhr?
        @load_path = @path.clone
      else
        @load_path = []
      end

      # figure out all the entries for this path
      @entries = @repo.fs.root(@rev).dir_entries(File.join(@load_path)).map { |key, value|
        #rev = root.node_created_rev(path)
        rev = @repo.fs.root(@rev).node_created_rev(File.join(@load_path, key))
        {
          :name => key,
          :created_revision => rev,
          :is_dir => value.kind == Svn::Core::NODE_DIR,
          :author => @repo.fs.prop(Svn::Core::PROP_REVISION_AUTHOR, rev).to_s,
          :date => @repo.fs.prop(Svn::Core::PROP_REVISION_DATE, rev),
          :log => @repo.fs.prop(Svn::Core::PROP_REVISION_LOG, rev).to_s,
          :filesize => value.kind == Svn::Core::NODE_FILE ? @repo.fs.root(@rev).file_length(File.join(@load_path, key)) : -1
        }
      }
  
      # if it's an xmlhttprequest, just send the string
      render(:partial => 'tree_js', :locals => {:entries => @entries}) if request.xhr?
    end
    
    def blame
      throw "not a source file" unless @info[:file?] && !@info[:binary?]
      @blame_path = "file://#{@repo_path}/#{File.join(@path)}"
      logger.debug(@blame_path + "--" + @rev.to_s)
    end

    def dirinfo
      throw "not a dir" unless @info[:dir?]
    end
    
    def source
      throw "not a file" unless @info[:file?]
      if !@info[:binary?]
        @file_contents = @repo.fs.root(@rev).file_contents(File.join(@path)) { |s| s.read }
      end
    end

    def history
      stop_revision = params['stop_revision'] && params['stop_revision'].length > 0 ? params['stop_revision'].to_i : 0
      @history = @repo.fs.history(File.join(@path), stop_revision, @rev).map { |hist|
        {
          :file => hist[0],
          :revision => hist[1],
          :author => @repo.fs.prop(Svn::Core::PROP_REVISION_AUTHOR, hist[1]).to_s,
          :date => @repo.fs.prop(Svn::Core::PROP_REVISION_DATE, hist[1]),
          :log => @repo.fs.prop(Svn::Core::PROP_REVISION_LOG, hist[1]).to_s,
          :filesize => @info[:type] == Svn::Core::NODE_FILE ? @repo.fs.root(hist[1]).file_length(hist[0]) : nil
        }
      }
    end

    
    def revdiff
      rev1 = params['rev1'].to_i
      rev2 = params['rev2'].to_i
      #rev1, rev2 = rev2, rev1 if rev1 < rev2
      path1 = nil
      path2 = nil
      
      @repo.fs.history(File.join(@path), [rev1,rev2,@rev].min, [rev1,rev2,@rev].max).each { |hist|
        path1 = hist[0] if hist[1] == rev1
        path2 = hist[0] if hist[1] == rev2
      }

      @udiff = get_udiff(rev1,path1,rev2,path2)
    end

    def diff
      if @info[:file?]
        @udiff = get_udiff(@rev, "/#{File.join(@path)}", params['against_rev'].to_i, params['against_path'])
        render(:action => 'filediff')
      else
        throw "can't do path differences yet"
      end
    end

    def revision
      dirdiff(@rev, '/', @rev-1, '/')
    end


    def dirdiff(rev1,path1,rev2,path2)
      @changes = RemoteSvn.get_directory_changes(@prev_rev, @rev)
      @info = RemoteSvn.get_revision_info(@rev)
      
      @added = (@changes[:added_dirs] + @changes[:added_files]).sort
      @deleted = (@changes[:deleted_dirs] + @changes[:deleted_files]).sort
      @copied = (@changes[:copied_dirs] + @changes[:copied_files]).sort
      @updated = (@changes[:updated_dirs] + @changes[:updated_files]).sort



      differ = Svn::Fs::FileDiff.new(@repo.fs.root(rev1), path1, @repo.fs.root(rev2), path2)
      @udiff = differ.unified("#{path2}@#{rev2}", "#{path1}@#{rev1}").split("\n")
      render(:action => 'dirdiff')
    end

    def dump
      @headers.update(
        'Content-Type'              => 'application/octet-stream',
        'Content-Disposition'       => 'attachment; filename="svndump.bin"',
        'Content-Transfer-Encoding' => 'binary'
      )
      render :text => Proc.new { |response, output|
        # this writes dump status stuff to /dev/null... if the server is windows,
        # it might not have /dev/null... instead you could try using
        # Tempfile.new("svn_dump_status") or something...
        @repo.dump_fs(output, File.new("/dev/null","w"), 0, @repo.fs.youngest_rev)
      }
    end
    
    def get_udiff(rev1,path1,rev2,path2)
      differ = Svn::Fs::FileDiff.new(@repo.fs.root(rev1), path1, @repo.fs.root(rev2), path2)
      differ.unified("#{path2}@#{rev2}", "#{path1}@#{rev1}").split("\n")
    end
    
    def get_info
      path = File.join(@path)
      type = @repo.fs.root(@rev).check_path(path)
      return nil if type == Svn::Core::NODE_NONE
      mime = @repo.fs.root(@rev).node_prop(path, Svn::Core::PROP_MIME_TYPE)
      rev = @repo.fs.root(@rev).node_created_rev(path)
      {
        :type => type,
        :file? => type == Svn::Core::NODE_FILE,
        :dir? => type == Svn::Core::NODE_DIR,
        :created_revision => rev,
        :mime => mime,
        :author => @repo.fs.prop(Svn::Core::PROP_REVISION_AUTHOR, rev),
        :date => @repo.fs.prop(Svn::Core::PROP_REVISION_DATE, rev),
        :log => @repo.fs.prop(Svn::Core::PROP_REVISION_LOG, rev),
        :filesize => type == Svn::Core::NODE_FILE ? @repo.fs.root(@rev).file_length(path) : nil,
        :binary? => mime == "application/octet-stream"
      }
    end
    
    def get_revisions
      # we could do just 0 --> @rev, but showing all seems nicer
      path = File.join(@path)
      @repo.fs.history(path, 0, @repo.fs.youngest_rev).map { |hist|
        {
          :file => hist[0],
          :revision => hist[1],
          :date => @repo.fs.prop(Svn::Core::PROP_REVISION_DATE, hist[1]),
          :author => @repo.fs.prop(Svn::Core::PROP_REVISION_AUTHOR, hist[1])
        }
      }
    end
end
